package com.sne.sys.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sne.common.entity.BaseModel;
import com.sne.common.entity.TreeNode;
import com.sne.sys.entity.SysMenu;
import com.sne.sys.entity.SysRoleMenu;
import com.sne.sys.mapper.SysMenuMapper;
import com.sne.sys.service.ISysMenuService;
import com.sne.sys.service.ISysRoleMenuService;
import com.sne.sys.vo.MetaVO;
import com.sne.sys.vo.RouteVO;
import com.sne.sys.vo.SysMenuVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 菜单 服务实现类
 * </p>
 *
 * @author fanghh
 * @since 2020-01-03
 */
@Slf4j
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements ISysMenuService {


    @Autowired
    private ISysRoleMenuService iSysRoleMenuService;

    @Autowired
    private SysMenuMapper sysMenuMapper;

    @Override
    public List<SysMenu> findMenuByRoleIds(List<Long> roleIdList) {
        List<SysMenu> menuList = new ArrayList<>();
        List<SysRoleMenu> roleMenuList = iSysRoleMenuService.list(Wrappers.<SysRoleMenu>query()
                .lambda()
                .in(SysRoleMenu::getRoleId, roleIdList));
        if (CollectionUtils.isNotEmpty(roleMenuList)) {
            List<Long> menusIdList = roleMenuList.stream().map(SysRoleMenu::getMenuId).collect(Collectors.toList());
            menuList = this.list(Wrappers.<SysMenu>query().lambda().in(SysMenu::getId, menusIdList));
        }
        return menuList;
    }

    @Override
    public List<TreeNode> findTree() {
        List<TreeNode> topNodeList = sysMenuMapper.selectList(Wrappers.<SysMenu>query()
                .lambda()
                .isNull(SysMenu::getParentId)
                .orderByAsc(SysMenu::getSort))
                .stream().map(x -> {
                    TreeNode treeNode = new TreeNode();
                    treeNode.setId(x.getId()).setLabel(x.getMenuName());
                    return treeNode;
                }).collect(Collectors.toList());
        this.findChildrenNode(topNodeList);
        return topNodeList;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean deleteMenu(Long id) {
        iSysRoleMenuService.remove(Wrappers.<SysRoleMenu>query()
                .lambda().eq(SysRoleMenu::getMenuId, id));
        return this.removeById(id);
    }


    @Override
    public List<RouteVO> findRoutes(List<Long> roleIds) {
        List<SysMenu> sysMenus = sysMenuMapper.findRoutes(roleIds);
        List<RouteVO> routeVOList = sysMenus.stream()
                .filter(x -> x.getParentId() == null && x.getType() != 2)
                .map(this::covertRouter).collect(Collectors.toList());
        sysMenus.removeIf(x -> x.getParentId() == null && x.getType() != 2);
        buildRoutes(routeVOList, sysMenus);
        return routeVOList;
    }

    @Override
    public List<SysMenuVO> findListTree(SysMenu menu) {
        LambdaQueryWrapper<SysMenu> queryWrapper = Wrappers.<SysMenu>query().lambda();
        if (StringUtils.isNoneBlank(menu.getMenuName())) {
            queryWrapper.like(SysMenu::getMenuName, menu.getMenuName());
        }
        if (menu.getState() != null) {
            queryWrapper.eq(SysMenu::getState, menu.getState());
        }
        List<SysMenu> menus = this.list(queryWrapper.orderByAsc(SysMenu::getSort)
                .orderByDesc(SysMenu::getCreateTime));
        List<SysMenuVO> menuVos = new ArrayList<>();
        List<Long> ids = menus.stream().map(BaseModel::getId).collect(Collectors.toList());
        menus.forEach(m -> {
            if (!ids.contains(m.getParentId())) {
                SysMenuVO menuVO = new SysMenuVO();
                BeanUtils.copyProperties(m, menuVO);
                menuVos.add(menuVO);
            }
        });
        menus.removeIf(m -> menuVos.stream().anyMatch(vo -> vo.getId().equals(m.getId())));
        buildTree(menuVos, menus);
        return menuVos;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean deleteIds(List<Long> ids) {
        iSysRoleMenuService.remove(Wrappers.<SysRoleMenu>query().lambda()
                .in(SysRoleMenu::getMenuId, ids));
        this.removeByIds(ids);
        return true;
    }

    @Override
    public List<String> findPermissions(List<Long> roleIds) {
        List<String> permissions = new ArrayList<>();
        List<SysRoleMenu> roleMenus = iSysRoleMenuService.list(Wrappers.<SysRoleMenu>query()
                .lambda().in(SysRoleMenu::getRoleId, roleIds));
        List<Long> ids = roleMenus.stream().map(SysRoleMenu::getMenuId).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(ids)) {
            List<SysMenu> menus = this.list(Wrappers.<SysMenu>query().lambda()
                    .in(SysMenu::getId, ids).eq(SysMenu::getType, 2));
            permissions = menus.stream().map(SysMenu::getPermissionCode).collect(Collectors.toList());
        }
        return permissions;
    }

    private void buildTree(List<SysMenuVO> menuVos, List<SysMenu> menus) {
        menuVos.forEach(vo -> {
            List<SysMenuVO> menuVOList = menus.stream()
                    .filter(x -> vo.getId().equals(x.getParentId()))
                    .map(this::covertMenuVO).collect(Collectors.toList());
            menus.removeIf(x -> vo.getId().equals(x.getParentId()));
            if (CollectionUtils.isNotEmpty(menuVOList)) {
                vo.setChildren(menuVOList);
                buildTree(vo.getChildren(), menus);
            } else {
                vo.setChildren(new ArrayList<>());
            }
        });
    }

    private SysMenuVO covertMenuVO(SysMenu menu) {
        SysMenuVO vo = new SysMenuVO();
        BeanUtils.copyProperties(menu, vo);
        return vo;
    }

    /**
     * 构建菜单路由
     *
     * @param topMenuVOList 顶级菜单
     * @param sysMenus      角色剩下菜单权限
     */
    private void buildRoutes(List<RouteVO> topMenuVOList, List<SysMenu> sysMenus) {
        topMenuVOList.forEach(route -> {
            List<RouteVO> routeVOList = sysMenus.stream()
                    .filter(x -> x.getType() != 2 && x.getParentId() != null
                            && route.getId().equals(x.getParentId()))
                    .map(this::covertRouter).collect(Collectors.toList());
            sysMenus.removeIf(x -> x.getType() != 2 && x.getParentId() != null &&
                    route.getId().equals(x.getParentId()));
            if (CollectionUtils.isNotEmpty(routeVOList)) {
                route.setChildren(routeVOList);
                buildRoutes(route.getChildren(), sysMenus);
            } else {
                route.setChildren(new ArrayList<>());
            }
        });
    }

    /**
     * 菜单转换为路由数据
     *
     * @param menu 菜单
     * @return RouteVO
     */
    private RouteVO covertRouter(SysMenu menu) {
        RouteVO routeVO = new RouteVO();
        MetaVO meta = new MetaVO();
        meta.setRoles(new ArrayList<>()).setTitle(menu.getMenuName())
                .setIcon(menu.getIcon()).setNoCache(Boolean.FALSE)
                .setBreadcrumb(Boolean.TRUE).setAffix(Boolean.FALSE);
        routeVO.setId(menu.getId()).setParentId(menu.getParentId()).setPath(menu.getUrl())
                .setHidden(!menu.getShowed()).setAlwaysShow(Boolean.FALSE)
                .setName(menu.getMenuCode())
                .setMeta(meta);
        routeVO.setComponent(menu.getType() == 0 && !menu.getExternalLink()? "Layout" : menu.getComponent());
        if (menu.getType() == 0 && menu.getParentId() == null) {
            routeVO.setPath("/" + menu.getUrl());
        }
        return routeVO;
    }

    private void findChildrenNode(List<TreeNode> topNodeList) {
        topNodeList.forEach(x -> {
            List<TreeNode> treeNodeList = sysMenuMapper.selectList(Wrappers.<SysMenu>query()
                    .lambda()
                    .eq(SysMenu::getParentId, x.getId())
                    .orderByAsc(SysMenu::getSort))
                    .stream().map(menu -> {
                        TreeNode treeNode = new TreeNode();
                        treeNode.setId(menu.getId())
                                .setLabel(menu.getMenuName());
                        return treeNode;
                    }).collect(Collectors.toList());
            if (!CollectionUtils.isEmpty(treeNodeList)) {
                x.setChildren(treeNodeList);
                this.findChildrenNode(treeNodeList);
            }
        });
    }
}
